package ru.samgtu.wst.plugin.sqlinj;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.logging.Logger;

import ru.samgtu.wst.httpclient.ConversationHandler;
import ru.samgtu.wst.httpclient.FetcherQueue;
import ru.samgtu.wst.model.ConversationID;
import ru.samgtu.wst.model.HttpUrl;
import ru.samgtu.wst.model.NamedValue;
import ru.samgtu.wst.model.Request;
import ru.samgtu.wst.model.Response;
import ru.samgtu.wst.model.StoreException;
import ru.samgtu.wst.plugin.Framework;
import ru.samgtu.wst.plugin.Hook;
import ru.samgtu.wst.plugin.Plugin;
import ru.samgtu.wst.util.Encoding;

public class SqlInjection implements Plugin, ConversationHandler {
	private Framework _framework = null;
	private SqlInjectionModel _model = null;
	private Logger _logger = Logger.getLogger(getClass().getName());
	private Thread _runThread;
	private FetcherQueue _fetcherQueue = null;
	private int _threads = 4;
	private int _delay = 100;
	public static int MINLENGTH = 3;

	private final static String[] vulnString = new String[] {
			"You have an error in your SQL syntax",
			"check the manual that corresponds to your MySQL server version for the right syntax to use near" };

	public SqlInjection(Framework framework) {
		_framework = framework;
		_model = new SqlInjectionModel(_framework.getModel());
	}

	@Override
	public void analyse(ConversationID id, Request request, Response response,
			String origin) {

		HttpUrl url = request.getURL();

		if (_framework.getModel().getConversationOrigin(id).equals(
				getPluginName())) {
			return;
		}

		String contentType = response.getHeader("Content-Type");
		if (contentType == null) {
			return;
		}
		if (!contentType.matches("text/.*")
				&& !contentType.equals("application/x-javascript")) {
			return;
		}
		byte[] responseContent = response.getContent();
		if (((responseContent == null) || (responseContent.length == 0))
				&& !response.getStatus().startsWith("3")) {
			return;
		}

		// prepare the response body, and headers
		String responseBody = null;
		if (responseContent != null) {
			responseBody = new String(responseContent).toUpperCase();
		}
		NamedValue[] headers = response.getHeaders();
		NamedValue[] ucHeaders = new NamedValue[headers.length];
		for (int i = 0; i < headers.length; i++) {
			ucHeaders[i] = new NamedValue(headers[i].getName().toUpperCase(),
					headers[i].getValue().toUpperCase());
		}

		String queryString = request.getURL().getQuery();
		if ((queryString != null) && (queryString.length() > 0)) {
			NamedValue[] params = NamedValue.splitNamedValues(queryString, "&",
					"=");
			checkParams(id, request, response, params, "GET", ucHeaders,
					responseBody);
		}

		if (request.getMethod().equals("POST")) {
			contentType = request.getHeader("Content-Type");
			if ("application/x-www-form-urlencoded".equals(contentType)) {
				byte[] requestContent = request.getContent();
				if ((requestContent != null) && (requestContent.length > 0)) {
					String requestBody = new String(requestContent);
					NamedValue[] params = NamedValue.splitNamedValues(
							requestBody, "&", "=");
					checkParams(id, request, response, params, "POST",
							ucHeaders, responseBody);
				}
			}
		}

	}

	private void checkParams(ConversationID id, Request request,
			Response response, NamedValue[] params, String paramLocation,
			NamedValue[] headers, String body) {
		if (params == null) {
			return;
		}
		for (int i = 0; i < params.length; i++) {
			if (body != null) {
				for (int j = 0; j < vulnString.length; j++) {
					if (!body.contains(vulnString[j])) {
						submitSqlTest(request, request.getMethod(), params[i]
								.getName());
					}
				}
			}
		}
	}

	private void submitSqlTest(Request origReq, String where, String param) {

		if (where.equals("GET")) {
			String testString = Encoding.urlEncode(_model.getSqlTestString());
			Request req = new Request(origReq);
			req.setURL(getURLwithTestString(req.getURL(), param, testString));
			_model.enqueueRequest(req, param);
		} else {
			String testString = _model.getSqlTestString();
			Request req = new Request(origReq);
			String oldContent = new String(req.getContent());
			StringBuffer newContent = new StringBuffer("");
			NamedValue[] contParams = NamedValue.splitNamedValues(oldContent,
					"&", "=");
			for (int i = 0; i < contParams.length; i++) {
				if (contParams[i].getName().equals(param)) {
					newContent.append(contParams[i].getName());
					newContent.append("=");
					newContent.append(testString);
					if (i != contParams.length - 1) {
						newContent.append("&");
					}
				} else {
					newContent.append(contParams[i].getName());
					newContent.append("=");
					newContent.append(contParams[i].getValue());
					if (i != contParams.length - 1) {
						newContent.append("&");
					}
				}
			}
			_logger.info("new content = " + newContent);
			req.setContent(newContent.toString().getBytes());
			_model.enqueueRequest(req, param);
			_logger.info("submit OK");
		}

	}

	private HttpUrl getURLwithTestString(HttpUrl url, String name, String value) {
		StringBuffer buf = new StringBuffer("?");

		String querystring = url.getQuery();
		if (querystring == null) {
			return null;
		}

		NamedValue[] params = NamedValue
				.splitNamedValues(querystring, "&", "=");
		for (int i = 0; i < params.length; i++) {
			if (params[i].getName().equals(name)) {
				buf.append(params[i].getName() + "=" + value);
			} else {
				buf.append(params[i].getName() + "=" + params[i].getValue());
			}
			if (i < params.length - 1) {
				buf.append("&");
			}
		}

		try {
			return new HttpUrl(url.getSHPP() + buf.toString());
		} catch (MalformedURLException e) {
			_logger.info("Exception: " + e);
			return null;
		}
	}

	@Override
	public void flush() throws StoreException {
	}

	@Override
	public String getPluginName() {
		return "Sql Injection";
	}

	@Override
	public Object getScriptableObject() {
		return null;
	}

	@Override
	public Hook[] getScriptingHooks() {
		return new Hook[0];
	}

	@Override
	public String getStatus() {
		return _model.getStatus();
	}

	@Override
	public boolean isBusy() {
		return _model.isBusy();
	}

	@Override
	public boolean isModified() {
		return _model.isModified();
	}

	@Override
	public boolean isRunning() {
		return _model.isRunning();
	}

	public SqlInjectionModel getModel() {
		return _model;
	}

	@Override
	public void run() {
		Request req;
		_model.setRunning(true);

		_model.setStatus("Started");
		_model.setStopping(false);
		_runThread = Thread.currentThread();
		// start the fetchers
		_fetcherQueue = new FetcherQueue(getPluginName(), this, _threads,
				_delay);
		_model.setRunning(true);
		while (!_model.isStopping()) {
			req = _model.dequeueRequest();
			if (req != null) {
				_fetcherQueue.submit(req);
			}
		}
		_model.setRunning(false);
		_model.setStatus("Stopped");

	}

	@Override
	public void setSession(String type, Object store, String session)
			throws StoreException {
		// TODO Auto-generated method stub

	}

	@Override
	public boolean stop() {
		_model.setRunning(false);
		return _model.isRunning();
	}

	@Override
	public void requestError(Request request, IOException ioe) {

	}

	@Override
	public void responseReceived(Response response) {
		String body = new String(response.getContent());
		System.err.print("FED->response reciver");
		ConversationID id = null;
		for (int i = 0; i < vulnString.length; i++) {
			if ((body != null) && (body.length() >= vulnString[i].length())
					&& (body.indexOf(vulnString[i]) != -1)) {
				_logger.info("SQL-injected: " + response.getRequest().getURL());
				id = _framework.addConversation(response.getRequest(),
						response, getPluginName());
				_model.setSqlVulnerable(id, response.getRequest().getURL());
				break;
			}
		}
	}

}
